/**
 * @file simple_message_server.c
 * Verteilte Systeme - TCP/IP Programmierübung
 *
 * tcpip server
 *
 * @author Theresa Meiksner - ic12b028 - theresa.meiksner@technikum-wien.at
 * @author Ronald Wimmer    - ic12b070 - ronald.wimmer@technikum-wien.at
 * @date 2013/12/09
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <netdb.h>
#include <unistd.h>

/* number of QUEUECAPACITY = maximum queue length for incoming connection */
#define QUEUECAPACITY 10

/**
 * \brief determines the server port by the give command line arguments
 *
 * \param argc - argument count
 * \param argv - argument variables
 *
 */
void getServerPortFromCmdLineArgs(int argc, char *argv[], char** serverPort);

/**
 * \brief method for removing process entries to prevent zombie processes
 *
 * called by signal handler
 *
 */
void removeProcesses();

/**
 * \brief displays usage
 *
 */
void showUsage();

/**
 * \brief sets up the servers listening socket with the given server port
 *
 * \param server_port - port for listening socket
 *
 */
void initiateListeningSocket(char *server_port);

/**
 * \brief server loop, where incoming connections are handled,
 *  (where new sockets are created, child processes are spawned etc.)
 *
 */
void startServer();

/**
 * \brief error handling routine
 *
 * \param location - info about the location, where the error happened
 * \param errorMessage - additional information about the specific error
 *
 */
void showErrorMessageAndExit(char* location, const char* errorMessage);

/* name of program for error logging */
char *programName = NULL;

/* the listening socket */
int socketFileDescriptor = 0;

int main(int argc, char *argv[]) {

	char *server_port;

	programName = argv[0];

   	getServerPortFromCmdLineArgs(argc, argv, &server_port);

   	initiateListeningSocket(server_port);

	startServer();

	return 0;
}

void getServerPortFromCmdLineArgs(int argc, char *argv[], char** serverPort) {
	int argument;
	unsigned long serverPortNumber;

	/* retrieve port arg from command line */
	while ((argument = getopt(argc, argv, "p:")) != -1)
		switch (argument) {
		case 'p':
			*serverPort = optarg;
			break;
		case '?':
			showUsage();
			exit(EXIT_SUCCESS);
		default:
			showUsage();
			exit(EXIT_FAILURE);
		}

	/* exit if no port was specified */
	if (*serverPort == NULL) {
		showUsage();
		exit(EXIT_FAILURE);
	}

	/* convert to number */
	serverPortNumber = (unsigned long) strtoul(*serverPort, NULL, 0);

	/* ensure port range */
	if (serverPortNumber < 1024 || serverPortNumber > 65535) {
		showUsage();
		exit(EXIT_FAILURE);
	}
}

void initiateListeningSocket(char *serverPort) {
	struct sigaction signalAction;
	struct addrinfo hints;
	struct addrinfo *socketAddress;

	/* passive open */
	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE;

	/* get address info for given port and hints */
	if ((getaddrinfo(NULL, serverPort, &hints, &socketAddress)) != 0) {
		showErrorMessageAndExit("getaddrinfo()", "error while calling getaddrinfo()");
	}

	/* step 1 - create socket */
	if ((socketFileDescriptor = socket(socketAddress->ai_family,
			socketAddress->ai_socktype, socketAddress->ai_protocol)) == -1) {
		showErrorMessageAndExit("socket()", "error while calling socket()");
	}

	/* step 2 - bind */
	if (bind(socketFileDescriptor, socketAddress->ai_addr, socketAddress->ai_addrlen) == -1) {
		showErrorMessageAndExit("bind()", "error while calling bind()");
	}

	/* step 3 - listen */
	if ((listen(socketFileDescriptor, QUEUECAPACITY)) == -1) {
		showErrorMessageAndExit("listen()", "error while calling listen()");
	}

	/* register signal handler 'removeProcesses' (called child process stops) */
	memset(&signalAction, 0, sizeof signalAction);
	signalAction.sa_handler = removeProcesses;
	sigemptyset(&signalAction.sa_mask);
	signalAction.sa_flags = SA_RESTART;

	if (sigaction(SIGCHLD, &signalAction, NULL) == -1) {
		showErrorMessageAndExit("sigaction()", "error while calling sigaction()");
	}
  	freeaddrinfo(socketAddress);
}

void startServer() {
	struct sockaddr_storage clientSocketAddress;
	socklen_t clientSocketSize;
	int newSocketFileDescriptor = 0;

	for(;;) {
		clientSocketSize = sizeof(clientSocketAddress);

		/* accept an incoming connection and create a new socket*/
		newSocketFileDescriptor = accept(socketFileDescriptor, (struct sockaddr *) &clientSocketAddress, &clientSocketSize);

		if (newSocketFileDescriptor == -1) {
			showErrorMessageAndExit("accept()", "error while creating new socket [accept()]");
		}

		/* fork a child process */
		if (!fork()) {
			/* this is the child process, so the listening socket can be closed */
			if (close(socketFileDescriptor) == -1)	{
				showErrorMessageAndExit("close()", "error while calling close() on listening socket");
			}
			/* duplicate the socket for the child program */
			if ((dup2(newSocketFileDescriptor, STDIN_FILENO)) == -1) {
				showErrorMessageAndExit("dup2()", "error while calling dup2() on new socket and STDIN_FILENO");
			}

			if ((dup2(newSocketFileDescriptor, STDOUT_FILENO)) == -1) {
				showErrorMessageAndExit("dup2()", "error while calling dup2() on new socket and STDOUT_FILENO");
			}

			if ((execlp("simple_message_server_logic", "simple_message_server_logic", NULL)) < 0) {
				showErrorMessageAndExit("execlp()", "error while calling external program simple_message_server_logic");
			}
			close(newSocketFileDescriptor);
			exit(EXIT_SUCCESS);
		} else {
			/* we are in the parent => we can close the newSocketFileDescriptor */
			close(newSocketFileDescriptor);
		}
	}
}

void showErrorMessageAndExit(char* location, const char* errorMessage) {
	if (socketFileDescriptor > 0) {
		close(socketFileDescriptor);
	}

	fprintf(stderr, "last errno: \n");
	perror(programName);
	fprintf(stderr, "Error occured in - %s: %s\n", location, errorMessage);
	exit(EXIT_FAILURE);
}

void removeProcesses() {
	while(waitpid(-1, NULL, WNOHANG) > 0);
}

void showUsage() {
	fprintf(stderr, "%s:\n", programName);
	fprintf(stderr, "Usage: simple_message_server option\n");
	fprintf(stderr, "Options:\n");
	fprintf(stderr, "\t-p, --port <port>\n");
	fprintf(stderr, "\t-h, --help\n");
	fprintf(stderr, "TCP Port Range = 1024 - 65535\n");
}
